/**
 * The MIT License
 *
 * Copyright (c) 2010 Benjamin Klum
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package org.helgoboss.scala_additions

import java.io._
import io.Source
import OperatingSystemClass._

object RichFile {
    def createTempDir(prefix: String, suffix: String = null) = {
        val tmpDir = File.createTempFile(prefix, suffix)
        tmpDir.delete
        tmpDir.mkdir
        tmpDir
    }
    
    def createTempFile(prefix: String, suffix: String = null) = {
        File.createTempFile(prefix, suffix)
    }
    
    def createNonExistingTempFile(prefix: String, suffix: String = null) = {
        val tmpFile = File.createTempFile(prefix, suffix)
        tmpFile.delete
        tmpFile
    }
    
    def tempDir = new File(System.getProperty("java.io.tmpdir"))
    def userHomeDir = new File(System.getProperty("user.home"))
}


class RichFile(file: File) {
    lazy val path = Path(file.getPath.replaceAll("""\\""", "/"))
    
    private lazy val isAbsoluteOnUnixOrWindows = {
        file.getPath.startsWith("/") || file.getPath.startsWith("""\""") || path.components.headOption.exists(_.contains(":"))
    }
    
    private case class DrivePath(driveLetter: String, remainder: Path)
    
    /**
     * Assumes path is absolute on unix or windows.
     */
    private def cygwinDrivePath = {
        if (path.components.size >= 2 && path.components.head == "cygdrive") {
            val CygwinDriveLetterPattern = "([a-z])".r
            path.components.tail.head match {
                case CygwinDriveLetterPattern(letter) => Some(DrivePath(letter, Path(path.components.drop(2))))
                case _ => None
            }
        } else {
            None
        }
    }
    
    /**
     * Assumes path is absolute on unix or windows. Returns lowercase letter.
     */
    private def windowsDrivePath = {
        if (path.components.size >= 1) {
            val WindowsDriveLetterPattern = "([A-Za-z]):".r
            path.components.head match {
                case WindowsDriveLetterPattern(letter) => Some(DrivePath(letter.toLowerCase, Path(path.components.tail)))
                case _ => None
            }
        } else {
            None
        }
    }
    
    private lazy val drivePath = cygwinDrivePath orElse windowsDrivePath
    
    private def driveLetter = drivePath.map(_.driveLetter)
    
    private def pathAfterDriveLetter = drivePath match {
        case Some(dp) => dp.remainder
        case None => Path(Nil)
    }
    
    /**
     * Returns the path expression in Unix style. 
     * 
     * No file system access is made to create the expression. Symbolic links are 
     * not resolved.
     *
     * A slash is used as separator. A trailing slash is inserted if the only 
     * path component is "X:" where X is supposed to be a drive letter. Cygwin
     * pathes are converted to its Windows equivalent. This path expression style 
     * is appropriate for Unix-only software and non-cygwin Windows software 
     * which prefers the slash, even on Windows.
     *
     * Examples: 
     * <ul>
     * <li><code>C:/</code></li>
     * <li><code>/opt/usr</code></li>
     * </ul>
     */
    lazy val unixPathExpression = {
        if (isAbsoluteOnUnixOrWindows) {
            /* Absolute path */
            driveLetter match {
                case Some(l) => l.toUpperCase + ":/" + pathAfterDriveLetter.components.mkString("/")
                case None => "/" + path.components.mkString("/")
            }
        } else {
            /* Relative path */
            path.components.mkString("/")
        }
    }
    
    /**
     * Returns the path expression in Windows style. 
     * 
     * No file system access is made to create the expression. Symbolic links are 
     * not resolved.
     *
     * A backslash is used as separator. A trailing backslash is inserted if the only
     * path component is "X:" where X is supposed to be a drive letter. Cygwin
     * pathes are converted to its Windows equivalent. This path expression style
     * is appropriate for Windows-only software and Unix software which prefers
     * the backslash, even on Unix.
     *
     * Examples: 
     * <ul>
     * <li><code>C:\</code></li>
     * <li><code>C:\Programme</code></li>
     * </ul>
     */
    lazy val windowsPathExpression = {
        if (isAbsoluteOnUnixOrWindows) {
            /* Absolute path */
            driveLetter match {
                case Some(l) => l.toUpperCase + ":\\" + pathAfterDriveLetter.components.mkString("\\")
                case None => "\\" + path.components.mkString("\\")
            }
        } else {
            /* Relative path */
            path.components.mkString("\\")
        }
    }
    
    
    /**
     * Returns the path expression in Unix style if the current platform is a Unix system
     * and in Windows if the current platform is a Windows system. 
     * 
     * Use this method for
     * passing paths to cross-platform software which prefers the native style on each system.
     * This is similar to <code>file.getCanonicalPath</code>. However, latter doesn't care
     * about converting cygwin paths into native Windows paths, might access the file system
     * and resolves symbolic links.
     */
    lazy val osDependentPathExpression = CurrentPlatform.osClass match {
        case Windows => windowsPathExpression
        case Unix => unixPathExpression
    }
    
    /**
     * Like unixPathExpression but converts "X:/foo" to "/cygdrive/x/foo". Use this method
     * for passing paths to Unix software or Windows software based on Cygwin.
     */
    lazy val cygwinCompatibleUnixPathExpression = {
        if (isAbsoluteOnUnixOrWindows) {
            /* Absolute path */
            driveLetter match {
                case Some(l) => 
                    val base = "/cygdrive/" + l.toLowerCase 
                    if (pathAfterDriveLetter.components.isEmpty) {
                        base
                    } else {
                        base + "/" + pathAfterDriveLetter.components.mkString("/")
                    }
                case None => "/" + path.components.mkString("/")
            }
        } else {
            /* Relative path */
            path.components.mkString("/")
        }
    }
    
    
    def /(childPath: Path) = new File(file, childPath.toString)

    def content_=(content: String) {
        val fw = new FileWriter(file)
        try { 
            fw.write(content) 
        } finally { 
            fw.close 
        }
    }
        
    def makeSureDirExistsAndIsEmpty {
        if (file.exists) {
            deleteRecursively
        }
        file.mkdirs
    }
    
    def makeSureDirExists {
        if (file.exists) {
            if (!file.isDirectory) {
                error("File with this name is existing but is not a directory")
            }
        } else {
            file.mkdirs
        }
    }
    
    def deleteRecursively: Boolean = {
        def deleteFile(subFile: File): Boolean = {
            if(subFile.isDirectory) {
                subFile.listFiles.foreach(deleteFile(_))
            }
            subFile.delete
        }
        deleteFile(file)
    }
        
    def content: String = {
        val source = Source.fromFile(file)
        val lines = source.mkString
        source.close()
        lines
    }
    
    def tree = new FileTreeIterator(root = file)
    
    def tree(walkOrder: Option[(File, File) => Boolean]) = new FileTreeIterator(root = file, walkOrder = walkOrder)
}
